import { EventDispatcher, LinearToneMapping, sRGBEncoding, Vector3, WebGLRenderer } from 'three';
import { HierarchyItem, OnLoad } from '.';
import { _Config } from './Config';
import { _Control } from './Control';
import { _Renderer } from './Renderer';
import { _ResLoader } from './ResLoader';
import Scene from './Scene';

/// ./model/pagani_huayra-bc/scene.gltf
export class SceneManager extends EventDispatcher {
  private renderer!: WebGLRenderer;
  private scene!: Scene;
  private frameEvent: Map<string, (fram: number) => boolean> = new Map();
  private readlyToRemove: string[] = [];
  private resizeEvent: Map<string, () => void> = new Map();
  currentFrame: number;
  lastFrame: number;
  /**
   * init 初始化
   */
  public init(dom: HTMLElement) {
    _Config.height = dom.clientHeight;
    _Config.width = dom.clientWidth;
    this.renderer = new WebGLRenderer({ antialias: true, alpha: true });
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(dom.clientWidth, dom.clientHeight);
    this.renderer.outputEncoding = sRGBEncoding;
    this.renderer.toneMapping = LinearToneMapping;
    this.renderer.toneMappingExposure = 0.8;
    dom.appendChild(this.renderer.domElement);
    _Config.canvasDom = this.renderer.domElement;
    this.lastFrame = Date.now();
    this.frame();
    window.addEventListener('resize', () => {
      _Config.height = dom.clientHeight;
      _Config.width = dom.clientWidth;
      this.scene.resize(_Config.width, _Config.height);
      this.resizeEvent.forEach((f) => f?.());
      _Renderer.setDirty();
    });
  }

  frame = () => {
    requestAnimationFrame(this.frame);
    this.currentFrame = Date.now();
    const offset = (this.currentFrame - this.lastFrame) * 0.001;
    this.lastFrame = this.currentFrame;
    this.excuteFrameEvent(offset);
    this.removeFrameEvent();
    _Renderer.render();
  };

  addFrameEvent(id: string, event: (offset: number) => boolean) {
    this.frameEvent.set(id, event);
  }
  addResizeEvent(id: string, onResize: () => void) {
    this.resizeEvent.set(id, onResize);
  }

  private excuteFrameEvent(offset: number) {
    this.frameEvent.forEach((f, v) => {
      const result = f(offset);
      if (result) {
        this.readlyToRemove.push(v);
      }
    });
  }
  private removeFrameEvent() {
    this.readlyToRemove.forEach((k) => {
      this.frameEvent.delete(k);
    });
    this.readlyToRemove.length = 0;
  }

  /**
   * loadModel
   */
  async loadGLTFModelToScene(
    filePath: string,
    fileName: string,
    cameraPos: Vector3,
    onProgress?: (event: ProgressEvent) => void
  ) {
    try {
      const gltf = await _ResLoader.loadGLTFPath(filePath, fileName, this.renderer, onProgress);
      if (!this.scene) {
        this.scene = new Scene(_Config.width, _Config.height, this.renderer);
      }
      this.scene.worldCamera.position.copy(cameraPos);
      this.scene.addChild(gltf.scene);
      this.scene.setAcheNodes();
      _Control.init(this.scene);
      _Control.focusToScene();
      _Renderer.init(this.renderer, this.scene.world, this.scene.worldCamera);
      this.dispatchEvent({ type: OnLoad });
    } catch (error) {
      console.error(error);
    }
  }

  getHierarchy() {
    const root = this.scene.world;
    const item = new HierarchyItem();
    item.id = root.uuid;
    item.label = root.name;
    item.children = [];
    item.toHierarchy(root, item.children);
    return item;
  }

  getCurrentVisiableNodeIDS() {
    return this.scene.visiableNodeIDs;
  }
  setSceneNodeState(nodeId: string, state: boolean) {
    if (this.scene.setNodeState(nodeId, state)) {
      _Renderer.setDirty();
    }
  }
}
